// NDSRom.cs:
// Class representing Nintendo DS Rom.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License.
// See LICENSE for more information.

using System;
using System.Collections.Generic;
using System.IO;

namespace SharpNitroLib.NDS
{
	/// <summary>
	/// Class representing Nintendo DS Rom.
	/// </summary>
    public class NDSRom
    {

    	/// <summary>
    	/// Create NDSRom from file.
    	/// </summary>
    	/// <param name="filename">Filename.</param>
    	/// <returns>NDS Rom object.</returns>
        public static NDSRom FromFile(String filename)
        {
            return FromFile(new FileInfo(filename));
        }

        /// <summary>
    	/// Create NDSRom from file.
    	/// </summary>
    	/// <param name="filename">File.</param>
    	/// <returns>NDS Rom object.</returns>
        public static NDSRom FromFile(FileInfo file)
        {
            var ndsRom = new NDSRom(file.OpenRead());
            return ndsRom;
        }

        /// <summary>
        /// Create NDSRom from stream.
        /// </summary>
        /// <param name="stream">Stream to use.</param>
        public NDSRom(Stream stream)
        {
            reader = new BinaryReader(stream);
            _romHeader = RomHeader.Read(reader);
            ScanDir("/", 0xF000);

        }

        /// <summary>
        /// List of filenames in rom file.
        /// </summary>
        public IEnumerable<String> Filenames
        {
            get
            {
                return _inodeCache.Keys;
            }
        }

        /// <summary>
        /// ROM header.
        /// </summary>
        public RomHeader Header
        {
            get {
                return _romHeader;
            }
        }

        /// <summary>
        /// Get data of file.
        /// </summary>
        /// <param name="filename">Filename of file.</param>
        /// <returns>Data of file.</returns>
        public byte[] GetFileData(String filename)
        {
            return GetFileData(_inodeCache[filename]);
        }

        /// <summary>
        /// Get data of file with FileId.
        /// </summary>
        /// <param name="fileId">Id of file.</param>
        /// <returns>Bytes of file.</returns>
        public byte[] GetFileData(UInt32 fileId)
        {
            var savedPos = reader.BaseStream.Position;

            reader.BaseStream.Seek(_romHeader.fat_offset + 8 * fileId, SeekOrigin.Begin);

            var begin = reader.ReadUInt32();
            var end = reader.ReadUInt32();

            reader.BaseStream.Seek(begin, SeekOrigin.Begin);

            var length = end - begin;
            var data = reader.ReadBytes((int)length);

            reader.BaseStream.Seek(savedPos, SeekOrigin.Begin);
            return data;
        }

        /// <summary>
        /// Binary reader for ROM stream.
        /// </summary>
        readonly BinaryReader reader;

        /// <summary>
        /// Cache for inodes.
        /// Key: filename.
        /// Value: fileId.
        /// </summary>
        readonly Dictionary<String, UInt32> _inodeCache = new Dictionary<string, uint>();
        
        /// <summary>
        /// ROM header.
        /// </summary>
        RomHeader _romHeader;

        /// <summary>
        /// Scan directory in ROM.
        /// </summary>
        /// <param name="prefix">Prefix of directory.</param>
        /// <param name="dir_id">Id of directory node.</param>
        private void ScanDir(String prefix, UInt32 dir_id)
        {
          
            var save_filepos = reader.BaseStream.Position;
            reader.BaseStream.Seek(_romHeader.fnt_offset + 8*(dir_id & 0xFFF), SeekOrigin.Begin);

            uint entry_start = reader.ReadUInt32();
            var entry_file_id = reader.ReadUInt16();
            var parent_id = reader.ReadUInt16();

            reader.BaseStream.Seek(_romHeader.fnt_offset + entry_start, SeekOrigin.Begin);

            for (var file_id=entry_file_id; ; file_id++)
            {
                var entry_type_name_length = reader.ReadByte();
                var name_length = entry_type_name_length & 127;
                bool entry_type_directory = (entry_type_name_length & 128) == 128 ? true : false;
                if (name_length == 0) break;
	
                var entry_name = new string(
                    reader.ReadChars(entry_type_name_length & 127));
                if (entry_type_directory)
                {
                    var dir_id2 = reader.ReadUInt16();

                    var strbuf = String.Empty;

                    strbuf = prefix + entry_name + "/";
			

                    ScanDir(strbuf, dir_id2);
                }
                else
                {

                    var strbuf = prefix + entry_name;
                    Console.WriteLine(strbuf);
                    _inodeCache[strbuf] = file_id;
                }
            }
            reader.BaseStream.Seek(save_filepos, SeekOrigin.Begin);

        }

        
    }
}